<template>
  <div class="hello">
    <div class="data-json mb-10">
      <p class="title">1.reduce同时实现map和filter</p>
      {{ reduceData }}
    </div>
    <div class="data-json mb-10">
      <p class="title">2.获取江苏省数据</p>
      {{ getProvinceData }}
    </div>
    <div class="data-json mb-10">
      <p class="title">3.获取所有省份数据,将数组转换成前端易解析json对象</p>
      {{ getInitDataToJson }}
    </div>
    <div class="data-json mb-10">
      <p class="title">4.快速排序</p>
      {{ getQuickSort }}
    </div>
    <div class="data-json mb-10">
      <p></p>
      <p class="title">5.es6的构造函数, 关键字class实现构造函数, 关键字extends实现继承, 举个栗子</p>
      {{ getEs6Person }}
    </div>
    <div class="data-json mb-10">
      <p></p>
      <p class="title">
        6.es5的构造函数,即寄生组合式继承,解释一下几声组合式:通过借用构造函数来继承属性和原型链来实现子继承父,
        举个栗子
      </p>
      <!-- {{ getEs5Person }} -->
    </div>
    <div class="data-json mb-10">
      <p></p>
      <p class="title">7.浅拷贝</p>
      <p class="mb-10">
        PS: for...of 与for....in的区别
        <br />1、使用for
        in会遍历数组所有的可枚举属性，包括原型。例如上栗的原型方法method和name属性，for
        in更适合遍历对象 <br />2、for of 适合遍历数组元素
      </p>
      <p class="mb-10">
        <label class="text">对象浅拷贝输出的结果:</label>
        <br />
        {{ dealCloneForm }}
      </p>
      <p class="mb-10">
        <label class="text">基本类型深拷贝输出的结果:</label>
        <br />
        {{ deepClone }}
      </p>
      <p class="mb-10">
        <label class="text">使用 JSON 对象的 parse 和 stringify</label>
        <br />
        {{ jsonClone }}
      </p>
      <p class="text">模板字符串之中还能调用函数</p>
    </div>
    <div class="data-json mb-10">
      <p class="title">8.$.nextTick()的回调延迟</p>
      <p>使用 nextTick 将回调延迟到下次 DOM 更新循环之后执行</p>
      <ul ref="box">
        <li v-for="(item, index) in dataArr" :key="index"></li>
      </ul>
    </div>
    <div class="data-json mb-10">
      <p class="title">9.js实现双向绑定效果</p>
      <p class="mb-10">
        1、Observe 的数据对象进行递归遍历，包括子属性对象的属性，都加上 setter 和 getter
      </p>
      <p class="mb-10">2、Compile 解析模板指令，将模板中的变量替换成数据，然后初始化渲染页面视图</p>
      <p class="mb-10">3、Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁</p>
      <p>4、为什么 Vue 3.0 中使用 Proxy 了？</p>
      <p class="mb-20">
        <label class="text ml-10">a.</label>
        vue中使用object.defineProperty进行双向数据绑定时，告知使用者监听数组，但是只是
        监听了数组的push()、pop()、shift()、splice()、sort()、reverse()这八种方法，其他数组的属性检测不到
        <br />
        <label class="text ml-10">b.</label>
        Object.definedProperty只能劫持对象的属性，因此对每个对象的属性进行遍历时，如果属性值
        也是对象则需要深度遍历，所以在比较proty能完整劫持对象的对比下，选择proxy
        <br />
        <label class="text ml-10">c.</label>
        proxy在vue2.0没有使用的原因，es6环境不够成熟，兼容性不好，此属性无法用polyfill来兼容
      </p>
      <input type="text" id="txt" placeholder="请输入绑定的内容" />
      <p id="show"></p>
    </div>
    <div class="data-json mb-10">
      <p class="title">10.ts中采用混合模式mixins</p>
      <!--组件 -->
      <view-mixins></view-mixins>
    </div>
    <div class="data-json mb-10">
      <p class="title">11.ts命名多个可能属性</p>
    </div>
    <div class="data-json mb-10">
      <p class="title">12.实现一个简单的数组拍平 flat 函数</p>
      <p>{{ arrayFlat(arr) }}</p>
    </div>
    <div class="data-json mb-10">
      <p class="title">13.用 reduce 实现 flat 函数</p>
      <p>{{ reduceFlat(arr) }}</p>
    </div>
    <div class="data-json mb-10">
      <p class="title">14. 如果原数组有空位，flat()方法会跳过空位。</p>
      <p>{{ filterFlat(filterArr) }}</p>
    </div>
    <div class="data-json mb-10">
      <p class="title">15. 通过传入整数参数控制“拉平”层数</p>
      <p>{{ flat(arr, Infinity) }}</p>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { CityList, possibleType } from "@/store/interface";
import { State, Mutation } from "vuex-class";
import { cloneForm, deepClone, JsonDeepClone } from "@utils/tools.ts";
import ViewMixins from "@views/ViewMixins.vue";
import { GET_CITY_LIST } from "@/store/types.ts";
import cityList from "@/data/city.js";

@Component({
  components: {
    ViewMixins
  }
}) // 不写则报错--Property or method "msg" is not defined on the instance but referenced during render
export default class BaseKnowledge extends Vue {
  public $refs!: {
    // 易错点 $refs: 确实!
    box: any;
  };
  @Prop()
  private msg!: string;
  @Prop(Number) initialEnthus!: number;
  // data属性
  private enthusiasm: number = this.initialEnthus;
  //data属性初始化
  private provinceData: Array<any> = []; // 计算属性
  private sortData: Array<number> = [1, 5, 8, 11, 2, 6];
  private reduceData: Array<any> = this.getReduceData(); // methods方法
  private dataArr: Array<any> = [];
  // es5构造函数的写法使用的两个变量name,age
  public name: string = "";
  public age: string = "";
  private arr: Array<any> = [
    1,
    2,
    3,
    4,
    [1, 2, 3, [1, 2, 3, [1, 2, 3]]],
    5,
    "string",
    { name: "弹铁蛋同学" }
  ];
  private filterArr: Array<any> = [1, 2, 3, 4];
  private param;
  // 技巧--》 使用类型别名
  //会报错: 因为length不是 sting和number类型的共有属性
  private getLength(param: possibleType) {
    return param.length;
  }

  @State
  private cityList!: Array<CityList>;
  @Mutation
  private GET_CITY_LIST!: (param: {}) => CityList;

  private formData: object = {
    code: "",
    dateCheckinStaff: "",
    dateCheckinStudent: "",
    dateEnd: "",
    dateEndTeaching: "",
    dateStart: "",
    dateStartTeaching: "",
    description: "",
    name: "",
    semesterNumber: 1,
    semesterYear: ""
  };
  private toData: object = {
    code: "2019.02",
    dateCheckinStaff: "2019-02-28",
    dateCheckinStudent: "2019-02-28",
    dateEnd: "2019-03-31",
    dateEndTeaching: "2019-03-30",
    dateStart: "2019-02-26",
    dateStartTeaching: "2019-02-27",
    description: "测试",
    id: 57,
    name: "2019~2020",
    semesterNumber: 2,
    semesterYear: 2019,
    weeks: 5 //多出来的属性
  };

  /**
   * es5的构造函数写法
   */
  private get getEs5Person(): string {
    var _this = this;
    function Person(name, age) {
      _this.name = name;
      _this.age = age;
    }
    Person.prototype = {
      eat: function() {
        return `My name is ${_this.name}, I am ${_this.age} years old, 正在吃饭`;
      },
      sleep: function() {
        return `My name is ${_this.name}, I am ${_this.age} years old, 正在睡觉`;
      }
    };
    let p = new Person("梁峻荣", "28");
    // 访问原型对象
    //console.log(Person.prototype);
    //console.log(p.__proto__); // __proto__仅用于测试，不能写在正式代码中
    //console.log(Person.prototype === p.__proto__); //注意错误写法: p._proto_ 输出结果: true
    return p.eat();
  }

  private get getEs6Person(): string {
    var _this = this;
    class Person2 {
      constructor(name, age) {
        _this.name = name;
        _this.age = age;
      }
      //原型对象的属性
      say() {
        return "My name is " + _this.name + ", I am " + _this.age + " years old";
      }
    }
    let p = new Person2("小燕子", 12);
    return p.say();
  }

  /**
   * 实现对象浅拷贝
   */
  private get dealCloneForm(): any {
    return cloneForm(this.formData, this.toData);
  }

  /**
   * 实现深拷贝
   */
  private get deepClone(): any {
    let a = [1, 2, 3, 4];
    let b = deepClone(a);
    a[0] = 2;
    return `输出a的值${a},
            输出b的值${b}`;
  }
  /**
   * json处理深拷贝
   */
  private get jsonClone(): any {
    let a = [0, 1, [2, 3], 4];
    let b = JsonDeepClone(a);
    a[0] = 1;
    a[2][0] = 1;
    return `输出a的值${a},
            输出b的值${b}`;
  }

  getReduceData(): Array<any> {
    // 返回类型
    const numbers = [10, 20, 30, 40];
    const result = numbers.reduce((filterNum: any, number) => {
      number = number * 2;
      if (number > 50) {
        filterNum.push(number);
      }
      return filterNum;
    }, []);
    return result;
  }

  increment() {
    this.enthusiasm++;
  }
  decrement() {
    if (this.enthusiasm > 1) {
      this.enthusiasm--;
    }
  }

  get exclamationMarks(): string {
    return Array(this.enthusiasm + 1).join("!");
  }

  get getQuickSort(): Array<number> {
    return this.quicksort(this.sortData);
  }

  quicksort(sortData: Array<number>): Array<number> {
    if (sortData.length < 1) {
      return sortData;
    }
    let povidIndex = Math.floor(sortData.length / 2); // 中间下标
    let povid = sortData.splice(povidIndex, 1)[0];
    console.log(povid, "获取快速排序的基准数");
    let left: Array<number> = [];
    let right: Array<number> = [];
    for (let i = 0; i < sortData.length; i++) {
      if (sortData[i] < povid) {
        left.push(sortData[i]);
        // Argument of type 'number' is not assignable to parameter of type 'never'.因为定义的left数组没有写类型,即错误写法:let left = [];
      } else {
        right.push(sortData[i]);
      }
    }
    //递归
    return this.quicksort(left).concat(povid, this.quicksort(right));
  }

  /**
   * 获取所有省份数据,将数组转换成前端易解析json对象
   */
  get getInitDataToJson(): Array<any> {
    let originData: any = [];
    if (this.cityList.length > 0) {
      this.cityList.map((item: any) => {
        let arr: any = {};
        let findIndex = originData.findIndex((data: any) => {
          return data.province === item.province;
        });
        if (findIndex !== -1) {
          originData.map((data: any) => {
            if (data.province === item.province) {
              data.citys.push({
                name: item.city,
                code: item.code
              });
            }
          });
        } else {
          arr.province = item.province;
          arr.citys = [
            {
              name: item.city,
              code: item.code
            }
          ];
          originData.push(arr);
        }
      });
    }

    return originData;
  }

  get getProvinceData(): Array<any> {
    let arr: any = [];
    let province: string = "";
    if (this.cityList.length > 0) {
      this.cityList.filter((item: any) => {
        if (item.province === "江苏省") {
          province = item.province;
          arr.push({
            name: item.city,
            code: item.code
          });
          return item;
        }
      });
      this.provinceData.push({
        province: province,
        citys: arr
      });
    }
    return this.provinceData;
  }
  private getData() {
    this.dataArr = [1, 2, 3];
    //易错点，首先需要声明$refs public $refs!: { box: any; };
    // 第一种写法: 直接报错 vue.runtime.esm.js?2b0e:1878 TypeError: Cannot set property 'innerHTML' of undefined
    //this.$refs.box.getElementsByTagName("li")[0].innerHTML = "hello";

    this.$nextTick(() => {
      this.$refs.box.getElementsByTagName("li")[0].innerHTML = "输出的结果为: hello";
    });
  }
  /**
   * js实现简单的双向绑定
   */
  private defineProperty(): void {
    let obj: any = {};
    //Object.defineProperty 接收三个参数：对象，属性名，配置对象
    Object.defineProperty(obj, "txt", {
      get: function() {
        return obj;
      },
      set: function(newValue) {
        let input: any = document.getElementById("txt");
        input.value = newValue;
        let show: any = document.getElementById("show");
        show.innerHTML = newValue;
      }
    });
    // document.addEventListener(
    //   "keyup",
    //   function(e: any) {
    //     obj.txt = e.target.value;
    //   },
    //   true
    // );
  }
  // concat + 递归
  private arrayFlat(arr): void {
    let arrResult: any = [];
    arr.forEach(item => {
      if (Array.isArray(item)) {
        arrResult = arrResult.concat(...item); // 递归
        // 或者用扩展运算符
        // arrResult.push(...arguments.callee(item));
      } else {
        arrResult.push(item);
      }
    });
    return arrResult;
  }
  //reduce实现flat
  private reduceFlat(arr): void {
    // 首先使用 reduce 展开一层
    arr.reduce((pre, cur) => pre.concat(cur), []);
    // [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "弹铁蛋同学" }];

    // 用 reduce 展开一层 + 递归
    const flat = arr => {
      return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
      }, []);
    };
    return flat(arr);
  }

  //如果原数组有空位，flat()方法会跳过空位。
  private filterFlat(arr): any {
    return arr.flat();
  }

  private flat(arr, num = 1): any {
    return num > 0
      ? arr.reduce((pre, cur) => {
          return pre.concat(Array.isArray(cur) ? this.flat(cur, num - 1) : cur);
        }, [])
      : arr.slice();
  }

  mounted() {
    this.getData();
    // js简单实现数据双向绑定
    this.defineProperty();
  }
  created() {
    //提交mutatison
    const data = cityList.datas;
    this.GET_CITY_LIST(data);
  }
}
</script>

<style scoped lang="stylus">
h3 {
  margin: 40px 0 0
}
ul {
  list-style-type: none
  padding: 0
}
li {
  display: inline-block
  margin: 0 10px
}
a {
  color: #42b983
}
.title {
  font-size: 12px
  font-weight: 700
  color: #f19000
}
.text {
  font-size: 12px
  font-weight: 700
  color: red
}
</style>
